#include "Game.h"

IGame* createGame( HINSTANCE hInst, HWND hWnd )
{
	return new Game( hInst, hWnd );
}

Game::Game( HINSTANCE hInst, HWND hWnd ) :
	m_input( hInst, hWnd ), m_hWnd(hWnd)
{
	m_hWnd = hWnd;
	// initialize camera
	m_camera.hand = Camera::RIGHTHANDED;

	m_camera.eye = float3( 0, 0, 20.f );
	m_camera.at  = float3( 0, 0, 0 );
	m_camera.up  = float3( 0, 1, 0 );

	m_camera.projectionType = Camera::PERSPECTIVE_FOV;
	m_camera.perspectiveFov.fovy = D3DXToRadian(45.f);
	m_camera.perspectiveFov.aspect = 1024.f/768.f;

	m_camera.zn = 1.0f;
	m_camera.zf = 100.0f;
}

Game::~Game()
{
	polview_ball.destroy();
	polview_box.destroy();
	m_d3dDevice = 0;
}

void Game::onCreateGraphics( D3DDevice device )
{
	m_d3dDevice = device;
	polview_ball.initialize( m_d3dDevice );
	polview_box.initialize( m_d3dDevice );

	m_d3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );

	float44 world = identity44();
	float44 view = m_camera.view();
	float44 proj = m_camera.projection();

	m_d3dDevice->SetTransform( D3DTS_WORLD, &world );
	m_d3dDevice->SetTransform( D3DTS_VIEW, &view );
	m_d3dDevice->SetTransform( D3DTS_PROJECTION, &proj );
	m_d3dDevice->GetViewport(&dxviewport);

	startGame();
}

void Game::onResetGraphics( D3DDevice device )
{
	//TODO: Add code to handle lost device ( resizing issue )

	m_d3dDevice = device;

	polview_ball.destroy();
	polview_ball.initialize( m_d3dDevice );
	polview_ball.setup( pol_ball, float4( 1.0f, 1.0f, 1.0f, 1.0f ) );

	polview_box.destroy();
	polview_box.initialize( m_d3dDevice );
	polview_box.setup( pol_box, float4( 1.0f, 1.0f, 1.0f, 1.0f ) );

	m_d3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );

	float44 view = m_camera.view();
	float44 proj = m_camera.projection();

	m_d3dDevice->SetTransform( D3DTS_VIEW, &view );
	m_d3dDevice->SetTransform( D3DTS_PROJECTION, &proj );
}

void Game::onLostGraphics()
{
}

void Game::onFrameMove(float dt)
{
	inputCheckMouse();
	updateBall(dt);
	updateBox();
	if(ai_state == GAME_AI_ON) updateAI();
	
}

void Game::onFrameRender(float dt)
{
	drawBall();
	drawBox();
	drawText();
}

void Game::onDestroyGraphics()
{
}
/*******************************************
START: Starts game sections
*******************************************/
void Game::startGame()
{
	ai_state = GAME_AI_ON;

	srand(time(NULL));

	//Initialize inputs
	initInputMouse();

	//Initialize game objects
	initBall();
	initBox();

	//Initialize text interfaces
	initText();

	//Initialize the players
	initPlayers();
	if(ai_state == GAME_AI_ON)	initAI();

	startNewRound();
}
void Game::startNewRound()
{
	pos_ball = float3(0,0,0);
	spd_ball = GAME_BALL_STARTSPEED;
	dir_y_ball = 1;
	dir_x_ball = 1;
	gameControl_state = GAME_STATE_PLAYING;
}
/*******************************************
INIT: Initializations
*******************************************/
void Game::initBall()
{
	pol_ball.push_back( float3( -1.0f/VIS_BALL_DIVSIZE, 1.0f/VIS_BALL_DIVSIZE, 0 ) );
	pol_ball.push_back( float3( -1.0f/VIS_BALL_DIVSIZE, -1.0f/VIS_BALL_DIVSIZE, 0 ) );
	pol_ball.push_back( float3( 1.0f/VIS_BALL_DIVSIZE, -1.0f/VIS_BALL_DIVSIZE, 0 ) );
	pol_ball.push_back( float3( 1.0f/VIS_BALL_DIVSIZE, 1.0f/VIS_BALL_DIVSIZE, 0 ) );

	polview_ball.setup( pol_ball, float4( 1.0f, 1.0f, 1.0f, 1.0f ) );

	pos_ball = float3(0,0,0);
	spd_ball = GAME_BALL_STARTSPEED;
	dir_y_ball = 1;
	dir_x_ball = 1;
}
void Game::initBox()
{
	poly::Polygon box;

	pol_box.push_back( float3( -1.0f/VIS_BOX_DIVSIZE, 1.0f/VIS_BOX_DIVSIZE, 0 ) );
	pol_box.push_back( float3( -1.0f/VIS_BOX_DIVSIZE, -1.0f/VIS_BOX_DIVSIZE, 0 ));
	pol_box.push_back( float3( 1.0f/VIS_BOX_DIVSIZE, -1.0f/VIS_BOX_DIVSIZE, 0 )	);
	pol_box.push_back( float3( 1.0f/VIS_BOX_DIVSIZE, 1.0f/VIS_BOX_DIVSIZE, 0 )	);

	polview_box.setup( pol_box, float4( 1.0f, 1.0f, 1.0f, 1.0f ) );

	add_box = false;
	remove_box = false;
}
void Game::initInputMouse()
{
	float sizeDiv=100.0f;
	pol_Mouse_Hit_Area.push_back( float3( -1.0f/sizeDiv, 1.0f/sizeDiv, 0 ) );
	pol_Mouse_Hit_Area.push_back( float3( -1.0f/sizeDiv, -1.0f/sizeDiv, 0 ));
	pol_Mouse_Hit_Area.push_back( float3( 1.0f/sizeDiv, -1.0f/sizeDiv, 0 )	);
	pol_Mouse_Hit_Area.push_back( float3( 1.0f/sizeDiv, 1.0f/sizeDiv, 0 )	);

	initInputMouseStates();
}
void Game::initInputMouseStates()
{
	inputMouse1Hold=false;
	inputMouse1Released=false;
	
	inputMouse2Hold=false;
	inputMouse2Released=false;
}
void Game::initPlayers()
{
	initPlayer1();
	initPlayer2();
}
void Game::initPlayer1()
{
	box_count_player1 = 0;
	player1_score = 0;
}
void Game::initPlayer2()
{
	box_count_player2 = 0;
	player2_score = 0;
}
void Game::initAI()
{
	ai_canPlaceBox == true;
}
void Game::initText()
{
	textInterface.setup(m_d3dDevice);
	textInterface.setPosition(float2(1,1));
	textInterface.setColor(float3(255,255,255));
	textInterface.setFace("Arial");
	textInterface.setHeight(14);
}
/*******************************************
CREATE: Creating elements
*******************************************/
void Game::createBox()
{	
	float3 position = inputMouseWorldCoord;
	poly::Polygon pol_box_hit = pol_box.transform(translation(inputMouseWorldCoord));
	vector<Box>::iterator Iter;
	Contact tempContact;

	bool intersects=false;//Variable to represent if there is an intersection or not
	
	//Intersection check
	for(Iter = boxVector.begin() ; Iter != boxVector.end() ; Iter++)
	{
		if((intersect(tempContact, pol_box.transform(translation((*Iter).position)), pol_box_hit)))
		{
			intersects=true;
			break;
		}
	}

	if(intersects)
	{
		//Separation limit check
		if(length(tempContact.seperation) > VIS_BOX_MAXSEPLIMIT)
		{
			Iter->strength++;
		}
		//If separation limit is below maximum, seperate the boxes and set non-intersecting.
		if(length(tempContact.seperation) <= VIS_BOX_MAXSEPLIMIT)
		{
			position += tempContact.seperation;
			intersects = false;
		}
	}

	//Create a box if there is no intersection
	if(!intersects)
	{
		Box BoxStructure;
		BoxStructure.player = 1;
		BoxStructure.strength = 1;
		BoxStructure.position = position;
		boxVector.push_back(BoxStructure);
	}
	add_box = false;
}
void Game::createBox(float3 position)
{	
	poly::Polygon pol_box_hit = pol_box.transform(translation(position));
	vector<Box>::iterator Iter;
	Contact tempContact;

	bool intersects=false;//Variable to represent if there is an intersection or not
	
	//Intersection check
	for(Iter = boxVector.begin() ; Iter != boxVector.end() ; Iter++)
	{
		if((intersect(tempContact, pol_box.transform(translation((*Iter).position)), pol_box_hit)))
		{
			intersects=true;
			break;
		}
	}

	if(intersects)
	{
		//Separation limit check
		if(length(tempContact.seperation) > VIS_BOX_MAXSEPLIMIT)
		{
			Iter->strength++;
		}
		//If separation limit is below maximum, seperate the boxes and set non-intersecting.
		if(length(tempContact.seperation) <= VIS_BOX_MAXSEPLIMIT)
		{
			position += tempContact.seperation;
			intersects = false;
		}
	}

	//Create a box if there is no intersection
	if(!intersects)
	{
		Box BoxStructure;
		BoxStructure.strength = 1;
		BoxStructure.position = position;
		BoxStructure.player = 2;
		boxVector.push_back(BoxStructure);
	}
	add_box = false;
}
/*******************************************
REMOVE: Removing elements
*******************************************/
void Game::removeBox()
{
		poly::Polygon pol_mouse_box = pol_Mouse_Hit_Area.transform(translation(inputMouseWorldCoord));

		vector<Box>::iterator Iter;
		Contact tempContact;

		//Check if there is a box under the cursor to remove
		for(Iter = boxVector.begin() ; Iter != boxVector.end() ; Iter++)
		{
			if(intersect(tempContact, pol_box.transform(translation((*Iter).position)), pol_mouse_box))
			{
				//Check for strength
				if((*Iter).strength>1)
				{
					((*Iter).strength)--;
					if((*Iter).player == 1)			box_count_player1--;
					else if((*Iter).player == 2)	box_count_player2--;
				}
				else if((*Iter).strength==1)
				{
					if((*Iter).player == 1)			box_count_player1--;
					else if((*Iter).player == 2)	box_count_player2--;
					
					boxVector.erase(Iter);
					
				}
				break;
			}
		}
	remove_box = false;
}
void Game::removeBox(std::vector<Box>::iterator& Iter)
{
	if((*Iter).player == 1)			box_count_player1--;
	else if((*Iter).player == 2)	box_count_player2--;

	boxVector.erase(Iter);

	remove_box = false;
}

/************************************
UPDATE: Update functions
************************************/
void Game::updateBall(float dt)
{
	int hitTest = hitBallBox();

	switch(hitTest)
	{
	case 1://Ball hits -X of screen edge
		player2_score++;
		gameControl_state = GAME_STATE_NEWROUND;
		break;
	case 0://Ball hits +X of screen edge
		player1_score++;
		gameControl_state = GAME_STATE_NEWROUND;
		break;
	case 10:
		spd_ball += 0.5f;
		dir_x_ball = dir_x_ball*-1;
		break;
	case 11:
		spd_ball += 0.5f;
		dir_x_ball = dir_x_ball*-1;
		break;
	case 3:
		dir_y_ball = dir_y_ball*-1;
		break;
	case 2:
		dir_y_ball = dir_y_ball*-1;
		break;
	case 12:
		spd_ball += 0.5f;
		dir_y_ball = dir_y_ball*-1;
		break;
	case 13:
		spd_ball += 0.5f;
		dir_y_ball = dir_y_ball*-1;
		break;
	}
	if(gameControl_state == GAME_STATE_PLAYING)
	{
		//Update position
		pos_ball.x += dir_x_ball*spd_ball*dt;
		pos_ball.y += dir_y_ball*spd_ball*dt;
	}
	else if(gameControl_state == GAME_STATE_NEWROUND)
	{
		startNewRound();
	}
}
void Game::updateBox()
{
	/*INFO on creating and removing boxes:
	If two boxes are overlapping, instead of drawing two boxes, it should add up to that box's strength (see the ideas.txt for
	the strength). Right now none of the gameplay (strength, points etc.) are coded. Feel free to do whatever
	you wish, change the structure or modify anything.
	*/

	//If Mouse1 is held, create a box...
	if((inputMouse1Hold)&&(box_count_player1 != GAME_BOX_MAXCOUNT))
	{add_box = true;}

	//...after it's released
	if((inputMouse1Released)&&(add_box))
	{
		createBox();
		box_count_player1++;
	}

	//If Mouse2 is held, remove a box...
	if(inputMouse2Hold)
	{remove_box = true;}

	//...after it's released
	if((inputMouse2Released)&&(remove_box))
	{removeBox();}
}
void Game::updateAI()
{
	if( ((pos_ball.x < -MAX_X+3.0f) &&
		(dir_x_ball == -1)) &&
		(box_count_player2 != GAME_BOX_MAXCOUNT)) 
	{
		createBox(float3(-MAX_X+2.0f,pos_ball.y,0));
		box_count_player2++;
	}
}

/************************************
HIT: Collision functions
************************************/
int Game::hitBallBox()
{
	/*TEST:OUT OF BOUNDS*/
	//Probably does not need to use collision interface for accuracy.
	//Should come up with a better way other than eye estimate for MAX_X and MAX_Y
	//Should change to left and right sides being a score for each player (Similar to PONG). 
	//Up and down should stay the same.
	if((pos_ball.x >= MAX_X) &&	(dir_x_ball == 1))		return 1;	//Out of bounds from +x side
	if((pos_ball.x <= -MAX_X)&& (dir_x_ball == -1))		return 0;	//Out of bounds from -x side
	if((pos_ball.y >= MAX_Y) &&	(dir_y_ball == 1))		return 3;	//Out of bounds from +y side
	if((pos_ball.y <= -MAX_Y)&& (dir_y_ball == -1))		return 2;	//Out of bounds from -y side

	Contact contactInfo;
	poly::Polygon pol_ball_t = pol_ball.transform(translation(pos_ball));
	poly::Polygon pol_box_t;

	/*TEST:BOX COLLISION*/
	vector<Box>::iterator Iter;
	for(Iter = boxVector.begin(); Iter != boxVector.end(); Iter++)
	{
		pol_box_t = pol_box.transform(translation(Iter->position));
		if(intersect(contactInfo, pol_ball_t, pol_box_t))
		{
			hitResolveBallBox(contactInfo);

			//If strength is gonna be 0, remove it
			if(--(Iter->strength) == 0)
			{
				removeBox(Iter);//Reduces the count inside
			}
			//Otherwise reduce the count
			else
			{
				if((*Iter).player == 1)			box_count_player1--;
				else if((*Iter).player == 2)	box_count_player2--;
			}

			if(contactInfo.axis == float3(-1,0,0))				return 10;	//Bounce back towards -x
			if(contactInfo.axis == float3(1,0,0))				return 11;	//Bounce back towards x
			if(contactInfo.axis == float3(0,-1,0))				return 12;	//Bounce back towards -y
			if(contactInfo.axis == float3(0,1,0))				return 13;	//Bounce back towards +y
		}
	}

	return -1;
}
void Game::hitResolveBallBox(Contact contactInfo)
{
	pos_ball -= contactInfo.seperation;
}
/************************************
DRAW: Rendering functions
************************************/
void Game::drawBall()
{
	float44 move = translation(pos_ball);
	polview_ball.render(move);
}
void Game::drawBox()
{
	float44 move;
	for(int i=0; i<boxVector.size(); i++)
	{
		move = translation(boxVector[i].position);
		polview_box.render(move);
	}
	
	if(inputMouse1Hold)
		{

			poly::Polygon pol_box_hit = pol_box.transform(translation(inputMouseWorldCoord));
			vector<Box>::iterator Iter;
			Contact tempContact;

			bool intersects=false;	//Variable to represent if there is an intersection or not
			
			for(Iter = boxVector.begin() ; Iter != boxVector.end() ; Iter++)
			{
				if(intersect(tempContact, pol_box.transform(translation((*Iter).position)), pol_box_hit))
				{
					intersects=true;
					break;
				}
			}
			if((intersects)&&(length(tempContact.seperation)<VIS_BOX_MAXSEPLIMIT))
			{
				move = translation(inputMouseWorldCoord + tempContact.seperation);
			}
			if(!intersects)
			{
				move = translation(inputMouseWorldCoord);
			}
			polview_box.render(move);
		}
}
void Game::drawText()
{
	static char buffer[1024];

	char Mouse1HoldState[6];
	char Mouse1ReleasedState[6];

	char Mouse2HoldState[6];
	char Mouse2ReleasedState[6];

	if(inputMouse1Hold)			strcpy(Mouse1HoldState, "True ");
	if(!inputMouse1Hold)		strcpy(Mouse1HoldState, "False");

	if(inputMouse1Released)		strcpy(Mouse1ReleasedState, "True ");
	if(!inputMouse1Released)	strcpy(Mouse1ReleasedState, "False");

	if(inputMouse2Hold)			strcpy(Mouse2HoldState, "True ");
	if(!inputMouse2Hold)		strcpy(Mouse2HoldState, "False");

	if(inputMouse2Released)		strcpy(Mouse2ReleasedState, "True ");
	if(!inputMouse2Released)	strcpy(Mouse2ReleasedState, "False");

	sprintf(buffer, "Player 1 # of Boxes: %d\n"
					"Player 2 # of Boxes: %d\n"
					"Player 1 Score: %d\n"
					"Player 2 Score: %d\n"
					"\nInfo: Left click adds\n"
					"a box, right click removes it.\n"
					"Try not to get scored."
					,
					box_count_player1,
					box_count_player2,
					player1_score,
					player2_score
					);

	textInterface.setText(buffer);
	textInterface.draw();
}
/**************************************
INPUT: Mouse & Keyboard Input functions
**************************************/
void Game::inputCheckMouse()
{
	inputMouse=m_input.mouseState();
	
	inputMouseToWorld();

	/*MOUSE1: Left Click**************************************/
	//If Mouse1 is clicked, then it's hold and not released
	if(inputMouse.buttons[0])
	{
		inputMouse1Hold = true;
		inputMouse1Released = false;
	}

	//If Mouse1 is not clicked
	if(!inputMouse.buttons[0])
	{
		//...and if Mouse1 was previously held, then it's released and not held
		if(inputMouse1Hold)
		{
			inputMouse1Released = true;
			inputMouse1Hold = false;
		}
		//...otherwise it's simply not held
		else
		{
			inputMouse1Hold = false;
		}
	}

	/*MOUSE2: Right Click**************************************/
	//If Mouse2 is clicked, then it's hold and not released
	if(inputMouse.buttons[1])
	{
		inputMouse2Hold = true;
		inputMouse2Released = false;
	}

	//If Mouse2 is not clicked...
	if(!inputMouse.buttons[1])
	{
		//...and if Mouse2 was previously held, then it's released and not held
		if(inputMouse2Hold)
		{
			inputMouse2Released = true;
			inputMouse2Hold = false;
		}
		//...otherwise it's simply not held
		else
		{
			inputMouse2Hold = false;
		}
	}
}
void Game::inputMouseToWorld()
{
	GetCursorPos(&inputMouseCursor);
	ScreenToClient(m_hWnd, &inputMouseCursor);

	float3 result;

	float44 worldMatrix = identity44();
	float3 VIn(inputMouseCursor.x,inputMouseCursor.y, m_camera.zn);
	float3 Vnear,Vfar;

	D3DXVec3Unproject(&Vnear, 
		&VIn,
		&dxviewport,
		&m_camera.projection(),
		&m_camera.view(),
		&worldMatrix
		);

	VIn.z = m_camera.zf;

	D3DXVec3Unproject(&Vfar, 
		&VIn,
		&dxviewport,
		&m_camera.projection(),
		&m_camera.view(),
		&worldMatrix
		);

	float3 Vdir = Vfar - Vnear;

	float t = (-(Vfar.z) / Vdir.z);

	inputMouseWorldCoord.x = Vfar.x + t * Vdir.x;
	inputMouseWorldCoord.y = Vfar.y + t * Vdir.y;
	inputMouseWorldCoord.z = 0;
}
